/*  tBookShelf
    (C) 2007  TvN (Volodymyr Tarasenko), e-mail: tvntsr@yahoo.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef LUA_INTERFACE_WRAPPER_HPP
#define LUA_INTERFACE_WRAPPER_HPP

#include <list>
#include <string>
#include <string.h>

#include <stdexcept>

extern "C"
{
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//:FIXME: ADD ERROR HANDLING!!!!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

class StackAutoLevel
{
    lua_State*  L;
    int         top;
public:
    StackAutoLevel(lua_State* s)
        : L(s)
    {
        top = lua_gettop(L);
    }

    ~StackAutoLevel()
    {
        if (top != 0)
            lua_settop(L, top);
    }

    void release()
    {
        top = 0;
    }
};

class LuaInterface
{
    friend class LuaClass;

    template<class T>
    friend class LuaField;
public:
    LuaInterface();

    ~LuaInterface() throw(); 

    template<class T>
    inline
    void   registerObject(T* obj, void* key);

    inline
    void   registerFunction(lua_CFunction func, const char* name);

    template<class T>
    inline
    void   registerVariable(const T& value , const char* name);

    template<class T>
    inline
    void   setFieldValue(const T& value, const char* name);

    template<class T>
    inline
    void   createTable(const char* name, T creator, bool local = false);

    void   createMetatable(const char* name, const luaL_reg* lib,
                           const char* index, const char* newindex);
    void   registerLibrary(const char* name, const luaL_reg* lib);

    void   addConfig(const std::string& cfg);

    void   recursiveConfig(const char* file_name); // Should it be public? 

    std::string getStringVariable(const char* var_name);

    // Created for simplicity, could be done via template lately
    int getIntegerVariable(const char* var_name);
    // The same as previous but could call lua function instread 
    // of reading value as variable
    int getIntegerData(const char* var_name);

    const char* getStringData(const char* var_name);

    void setIntegerVariable(const char* var_name, const int& value);

protected:
    inline void luaError()
    {
        const char* error = lua_tostring(L, -1);
        lua_pop(L, 1);

        throw std::runtime_error(error);
    }

    void standFunction(const char* name);
    void standFunction(const char* oname, const char* name);

    template <typename T>
    inline
    bool getLuaValue(T& data, const char* name);

    void doLuaCall(int nargs, int nresults, int errfunc = 0);

protected:
    lua_State*  L;

};


class LuaClass
{
protected:
    LuaClass(LuaInterface& lua, const std::string& name);

public:
    LuaClass(const LuaClass& l)
        : mClassName(l.mClassName),
          L(l.L)
    {    }
    
    LuaClass& operator=(const LuaClass& arg)
    {
        if (this == &arg)
            return *this;

        if (arg.mClassName != mClassName)
        {
            throw std::runtime_error("Try assign different LuaClasses");
        }

        return *this;
    }

    virtual
    ~LuaClass()
    {
        std::cerr << "~LuaClass(): " << (void*)this << std::endl;
    }

    template<class T>
    static T* createInstance(LuaInterface& lua/*, const char* meta_name*/)
    {
        lua_State*  L = lua.L;

        void* ptr = lua_newuserdata(L, sizeof(T));
        if(ptr == NULL)
            return 0;

        T* t = new(ptr)T(lua); 
        if (t == 0)
        {
            return t;
        }

        luaL_getmetatable(L, t->mClassName.c_str());
        lua_setmetatable(L, -2);

        return t;
    }
    
    template<class T>
    static T* createInstanceAsVariable(LuaInterface& lua, 
                                       const char*   var_name)
    {
        T* t = createInstance<T>(lua);
        if (t == 0)
        {
            return t;
        }

        lua_setglobal(lua.L, var_name);    

        return t;
    }

    template <typename T>
    static T* getGlobal(LuaInterface& lua, 
                        const char*   var_name, 
                        const char*   meta_name)
    {
        lua_State*  L = lua.L;

        lua_getglobal(L,var_name);

        return getInstance<T>(L, -1, meta_name);
    }

    template <typename T>
    static T* getInstance(LuaInterface& lua, int arg, const char* meta_name)
    {
        lua_State*  L = lua.L;

        return getInstance<T>(L, arg, meta_name);
    }

    template <typename T>
    static T* getInstance(lua_State* L, int arg, const char* meta_name)
    {
        void *ud = luaL_checkudata(L, arg, meta_name);
        luaL_argcheck(L, ud != NULL, arg, "Wrong type");

        T* t = reinterpret_cast<T*>(ud);

        return t;
    }
    const char* className() const
    {
        return mClassName.c_str();
    }

    virtual 
    int getField(const char* name, lua_State* L) = 0;

    virtual 
    int setField(const char* name, lua_State* L) = 0;

    int rawField(const char* name, lua_State* L);

    int index(lua_State* L);
    int newIndex(lua_State* L);

private:
//    LuaClass(const LuaClass&);

//    LuaClass& operator=(const LuaClass&);

private:
    std::string mClassName;
    lua_State*  L;
};

template <class T>
class LuaField
{
public:
    LuaField(LuaInterface& lua, const char* name)
        : mL(lua.L),
          mField(0),
          mFieldName(name),
          mRef(LUA_NOREF)
    {
        mField = LuaClass::createInstance<T>(lua); 
        mRef   = luaL_ref (lua.L, LUA_REGISTRYINDEX);
    }

    virtual
    ~LuaField()
    {
        if (mRef != LUA_NOREF)
            lua_unref(mL, mRef);
    }
    
    T& operator=(const T& val)
    {
        if (mField)
        {
            *mField = val;
        }
        return val;
    }

    int getValue(lua_State* L)
    {
        if (!lua_isuserdata(L, 1))
        {
            luaL_error(L, "Base argument of wrong type");
            return 0;
        }
        
        lua_pop(L, 1);
        lua_getref(L, mRef);

        return 1;
    }
    int setValue(lua_State* L)
    {
        if (!checkArgName(L))
        {
            return 0;
        }

        T* arg = LuaClass::getInstance<T>(L, 3, mField->className());
        
        if (arg)
        {
            *mField = *arg;
        }
        lua_pop(L, 2);

        return 1;
    }

    T& operator*() const throw()
    {
        return *mField;
    }
    T* operator->() const throw()
    {
        return mField;
    }
protected:
    bool checkArgName(lua_State* L, int arg = 2)
    {
        const char* field_name = lua_tostring(L, arg);

        return (field_name) ? 
            (strcmp(field_name, mFieldName.c_str()) == 0) : false;
    }

private:
    lua_State*  mL;
    T*          mField;
    std::string mFieldName;
    int         mRef;
};


// --------------------------------------------------------------------
//   IMPLEMENTATION 
// --------------------------------------------------------------------
template<class T>
inline
void LuaInterface::registerObject(T* obj, void* key)
{
    lua_pushlightuserdata(L, key);
    lua_pushlightuserdata(L, (void *)obj);
    lua_settable(L, LUA_REGISTRYINDEX);
}

inline
void LuaInterface::registerFunction(lua_CFunction func, const char* name)
{
    lua_pushcfunction(L, func);
    lua_setglobal(L, name);
}

template<>
inline
void LuaInterface::registerVariable<int>(const int& value , const char* name)
{
    lua_pushinteger(L, value);
    lua_setglobal(L,   name);    
}

template<>
inline
void LuaInterface::registerVariable<double>(const double& value,
                                            const char*   name)
{
    lua_pushnumber(L, value);
    lua_setglobal(L,   name);    
}

template<>
inline
void LuaInterface::registerVariable<bool>(const bool& value,
                                          const char*   name)
{
    lua_pushboolean(L, value);
    lua_setglobal(L,   name);    
}

typedef char* pchar;
template<>
inline
void LuaInterface::registerVariable<pchar>(const pchar &value,
                                           const char* name)
{
    lua_pushstring(L, value);
    lua_setglobal(L,  name);    
}

template<>
inline
void LuaInterface::registerVariable<std::string>(const std::string &value,
                                                 const char*   name)
{
    lua_pushlstring(L, value.c_str(), value.size());
    lua_setglobal(L,  name);    
}

template<>
inline
void LuaInterface::setFieldValue<int>(const int& value, const char* name)
{
    lua_pushstring(L,  name);
    lua_pushinteger(L, value);
    lua_settable(L, -3);
}

template<>
inline
void LuaInterface::setFieldValue<bool>(const bool& value, const char* name)
{
    lua_pushstring(L,  name);
    lua_pushboolean(L, value);
    lua_settable(L, -3);
}

template<>
inline
void LuaInterface::setFieldValue<pchar>(const pchar& value, const char* name)
{
    lua_pushstring(L, name);
    lua_pushstring(L, value);
    lua_settable(L, -3);
}

template<class T>
inline
void LuaInterface::createTable(const char* name, T creator, bool local)
{
    if (local)
    {
        lua_pushstring(L, name);
    }
        
    lua_newtable(L);
    creator(*this);

    if (local)
    {
        lua_settable(L, -3);
    }
    else
    {
        lua_setglobal(L, name);
    }
}


template <>
inline
bool LuaInterface::getLuaValue<bool>(bool& data, const char* name)
{
    StackAutoLevel tr(L);

    lua_getfield(L, -1, name);
    if (lua_isnil(L, -1))
    {
        return false;
    }

    // get value of field
    if (!lua_isboolean(L, -1))
    {
        throw std::runtime_error(std::string(__FUNCTION__)+
                                 "->Lua error: field is not number");
    }
    data = lua_toboolean(L, -1);
    
    return true;
}

template <>
inline
bool LuaInterface::getLuaValue<int>(int& data, const char* name)
{
    StackAutoLevel tr(L);

    lua_getfield(L, -1, name);
    if (lua_isnil(L, -1))
    {
        return false;
    }

    // get value of field
    if (!lua_isnumber(L, -1))
    {
        throw std::runtime_error(std::string(__FUNCTION__)+
                                 "->Lua error: field is not number");
    }
    data = (int)lua_tonumber(L, -1);
    
    return true;
}

template <>
inline
bool LuaInterface::getLuaValue<unsigned int>(unsigned int& data, 
                                             const char*   name)
{
    StackAutoLevel tr(L);

    lua_getfield(L, -1, name);
    if (lua_isnil(L, -1))
    {
        return false;
    }

    // get value of field
    if (!lua_isnumber(L, -1))
    {
        throw std::runtime_error(std::string(__FUNCTION__)+
                                 "->Lua error: filed is not number");
    }

    data = (unsigned int)lua_tonumber(L, -1);
    
    return true;
}

template <>
inline
bool LuaInterface::getLuaValue<std::string>(std::string& data, 
                                            const char*  name)
{
    StackAutoLevel tr(L);

    lua_getfield(L, -1, name);

    if (lua_isnil(L, -1))
    {
        return false;
    }

    // get value of field
    if (!lua_isstring(L, -1))
    {
        throw std::runtime_error(std::string(__FUNCTION__)+
                                 "->Lua error: field is not string");
    }

    const char *b = lua_tolstring(L, -1, 0);
    if (b)
        data = b;
    else
    {
        data = "";
    }

    return true;
}

#endif //LUA_INTERFACE_WRAPPER_HPP
